/*
 * Client.java
 *
 * Created on 22 mai 2007, 12:17
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package bibliotheque.client;

import bibliotheque.client.remote.ClientRemoteIS;
import bibliotheque.document.DocumentId;
import bibliotheque.document.DocumentSummary;
import bibliotheque.document.Document;
import bibliotheque.events.ErrorOccuredEvent;
import bibliotheque.events.ErrorOccuredListener;
import bibliotheque.events.StateChangedEvent;
import bibliotheque.events.StateChangedListener;
import bibliotheque.events.TransactionResultAddOccuredEvent;
import bibliotheque.events.TransactionResultConsultOccuredEvent;
import bibliotheque.events.TransactionResultFreeOccuredEvent;
import bibliotheque.events.TransactionResultInvalidateOccuredEvent;
import bibliotheque.events.TransactionResultOccuredListener;
import bibliotheque.events.TransactionResultReadOccuredEvent;
import bibliotheque.events.TransactionResultTakeOccuredEvent;
import bibliotheque.events.TransactionResultValidateOccuredEvent;
import bibliotheque.nameserver.remote.NameServerRemoteIC;
import bibliotheque.transaction.TransactionId;
import bibliotheque.server.remote.ServerRemoteIC;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;



/**
 *
 * @author guillaume
 */
public class Client extends UnicastRemoteObject implements  ClientRemoteIS {
    
    private enum State {INITIATING_CONNEXION, WAITING_FOR_ACTION, SEARCHING, SEARCH_COMPLETE, ADDING_DOCUMENT, FREEING_DOCUMENT, VALIDATING_TRANSACTION, INVALIDATING_TRANSACTION, READING_TRANSACTION};
    public enum TransactionType {CONSULT,FREE,READ,ADD,TAKE};
    
    private final String ip;
    
    private final String service;
    private Object result;
    private ArrayList<TransactionResultOccuredListener> transaction_result_listener;
    private ArrayList<StateChangedListener> state_changed_listener;
    private ArrayList<ErrorOccuredListener> error_occured_listener;
    private State state;
    private Registry registry;
    private TransactionId transactionId;
    
    
    private ServerRemoteIC server;
    private NameServerRemoteIC nameserver;
    
    /**
     * Creates a new instance of Client
     * @param service Client's service
     * @param rmi_registry rmiregistry connexion
     * @throws java.rmi.RemoteException
     */
    public Client(String service, String ip) throws RemoteException {
        this.ip = ip;
        this.service = service;
        this.transaction_result_listener = new ArrayList<TransactionResultOccuredListener>();
        this.state_changed_listener = new ArrayList<StateChangedListener>();
        this.error_occured_listener = new ArrayList<ErrorOccuredListener>();
        this.state = State.WAITING_FOR_ACTION;
        //this.registry = rmi_registry;
    }
    
    /*########################################################################*/
    /*                                                                        */
    /*                              Listener                                  */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * Add an ErrorOccuredListener to the Object
     * @param listener ErrorOccuredListener to add
     */
    public void AddErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.add(listener);
    }
    
    /**
     * Remove an ErrorOccuredListener to the Object
     * @param listener ErrorOccuredListener to remove
     */
    public void RemoveErrorOccuredListener(ErrorOccuredListener listener) {
        this.error_occured_listener.remove(listener);
    }
    
    
    /**
     * To specifie that a new error occured
     * @param error String that describe the error
     */
    private void ErrorOccured(String error){
        ErrorOccuredEvent event = new ErrorOccuredEvent(this, error);
        for(int i=0;i<this.error_occured_listener.size();i++) {
            this.error_occured_listener.get(i).ErrorOccured(event);
        }
    }
    
    /**
     * Add a StateChangedListner to the Object
     * @param listener StateChangedListener to add
     */
    public void AddStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.add(listener);
        this.StateChanged(this.state.toString());
    }
    
    /**
     * Remove a StateChangedListner to the Object
     * @param listener StateChangedListener to remove
     */
    public void RemoveStateChangedListener(StateChangedListener listener) {
        this.state_changed_listener.remove(listener);
    }
    
    
    /**
     * To specifie that the current state changed
     * @param state String that describe the nex state
     */
    private void StateChanged(String state){
        StateChangedEvent event = new StateChangedEvent(this, state);
        for(int i=0;i<this.state_changed_listener.size();i++) {
            this.state_changed_listener.get(i).StateChanged(event);
        }
    }
    
    /**
     * Add a TransactionResultOccuredListner to the Object
     * @param listener TransactionResultOccuredListner to add
     */
    public void AddTransactionResultOccuredListener(TransactionResultOccuredListener listener) {
        this.transaction_result_listener.add(listener);
    }
    
    /**
     * Remove a TransactionResultOccuredListner to the Object
     * @param listener TransactionResultOccuredListner to remove
     */
    public void RemoveTransactionResultListener(TransactionResultOccuredListener listener) {
        this.transaction_result_listener.remove(listener);
    }
    
    //TODO : Private
    /**
     * To specifie a new result for a transaction
     * @param result Result of the transaction
     */
    
    private void TransactionResultAddOccured(Boolean resultAdd) {
        TransactionResultAddOccuredEvent trans = new TransactionResultAddOccuredEvent(this,resultAdd);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultAddOccured(trans);
        }
    }
    
    private void TransactionResultConsultOccured(ArrayList<DocumentSummary> resultConsult, TransactionId transactionId) {
        this.transactionId = transactionId;
        TransactionResultConsultOccuredEvent trans = new TransactionResultConsultOccuredEvent(this,resultConsult);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultConsultOccured(trans);
        }
    }
    
    private void TransactionResultFreeOccured(Boolean resultFree,DocumentId docId) {
        TransactionResultFreeOccuredEvent trans = new TransactionResultFreeOccuredEvent(this,resultFree,docId);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultFreeOccured(trans);
        }
    }
    
    private void TransactionResultReadOccured(DocumentId docId, String resultRead) {
        TransactionResultReadOccuredEvent trans = new TransactionResultReadOccuredEvent(this,docId,resultRead);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultReadOccured(trans);
        }
    }
    
    private void TransactionResultTakeOccured(ArrayList<DocumentSummary> resultTake, TransactionId transactionId) {
        this.transactionId = transactionId;
        TransactionResultTakeOccuredEvent trans = new TransactionResultTakeOccuredEvent(this,resultTake);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultTakeOccured(trans);
        }
    }
    
    private void TransactionResultValidateOccured(Boolean resultValidate) {
        TransactionResultValidateOccuredEvent trans = new TransactionResultValidateOccuredEvent(this,resultValidate);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultValidateOccured(trans);
        }
    }
    
    private void TransactionResultInvalidateOccured(Boolean resultInvalidate) {
        TransactionResultInvalidateOccuredEvent trans = new TransactionResultInvalidateOccuredEvent(this,resultInvalidate);
        for (int i = 0; i<this.transaction_result_listener.size(); i++) {
            this.transaction_result_listener.get(i).TransactionResultInvalidateOccured(trans);
        }
    }
    
    /*########################################################################*/
    /*                                                                        */
    /*                            Connexion                                   */
    /*                                                                        */
    /*########################################################################*/
    
    
    
    
    /**
     * Initiate the connexion to the NameServer. it will set up the two mains variables nameServer and server to deal with
     * @param Service Name of the service you will connect to
     */
    public void InitiateConnexion(String Service, String ip, int port) {
        
        try {
            this.registry = LocateRegistry.getRegistry(ip,port);
            
        } catch (RemoteException ex) {
            ex.printStackTrace();
        }
        
        this.state = State.INITIATING_CONNEXION;
        this.StateChanged(this.state.toString());
        try {
            /* rmiRegistry connexion, NameServer is captured */
            this.nameserver = (NameServerRemoteIC)this.registry.lookup(service);
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException during the lookup function: \n" + ex.getMessage() );
        } catch (NotBoundException ex) {
            this.ErrorOccured("NotBoundException during the lookup function: \n" + ex.getMessage() );
        }
        try {
            /* Closest Server*/
            this.server = this.nameserver.GetClosestServer(this.ip);
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException during NameServer.GetClosestServer: \n" + ex.getMessage() );
        } 
        this.StateChanged("CONNEXION Initiated successfully\n");
        
        
        
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                              Take                                      */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * To propagate a new wave for a withdraw on a document
     *
     * @param search String that defines the search
     */
    public void WithDraw(String search) {
        try {
            this.server.Withdraw(this.ip,this,this.GetArrayList(search));
            this.state = State.SEARCHING;
            this.StateChanged(this.state.toString());
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nTAKE failed because Client.WithDraw(String search) returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    
    /**
     * Set the result from a Take action on the Client
     * @param transactionId Id of the transaction which result of the research
     * @param result DocumentSummary List of the result
     */
    public void SetTakeResult(ArrayList<DocumentSummary> result, TransactionId transactionId) {
        this.state = State.SEARCH_COMPLETE;
        this.StateChanged(this.state.toString());
        this.TransactionResultTakeOccured(result,transactionId);        
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                               Read                                     */
    /*                                                                        */
    /*########################################################################*/
    
    
    /**
     * Read the document identified by DocumentId
     * @param docId Document Id of the document to read
     */
    public void Read(DocumentId docId){
        try {
            this.server.Read(this.transactionId,docId);
            System.out.println("READ SET : numero : "+docId.GetId() + "     serveur : " + docId.GetIp());
            this.state = State.READING_TRANSACTION;
            this.StateChanged(this.state.toString());
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nREAD failed because Client.Read(DocumentId docId) returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    /**
     * Set the result from a Read action on the Client
     * @param result Entire Document to read
     */
    public void SetReadResult(DocumentId docId, String content) {
        this.TransactionResultReadOccured(docId, content);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
        
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                               Add                                      */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * To add a new document on the server
     * @param title title of the document
     * @param summary summary of the document
     * @param content content of the document
     */
    public void Add(String title, String summary, String content) {
        try {
            this.server.Add(this,title,summary,content);
            this.state = State.ADDING_DOCUMENT;
            this.StateChanged(this.state.toString());
            
        }
        
        catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nADD failed because Client.Add(String title, String summary, String content) returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    /**
     * Set the result from a Add action on the Client
     * @param result Boolean to give the result of the Add
     */
    public void SetAddResult(Boolean result) {
        this.TransactionResultAddOccured(result);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    /*########################################################################*/
    /*                                                                        */
    /*                              Consult                                   */
    /*                                                                        */
    /*########################################################################*/
    
    
    /**
     * To propagate a new wave for a simple consultation
     *
     * @param search String that defines the search
     */
    public void Consult(String search) {
        try {
            System.out.println(this.ip);
            System.out.println(this.GetArrayList(search).toString());
            this.server.Consult(this.ip,this,this.GetArrayList(search));
            this.state = State.SEARCHING;
            this.StateChanged(this.state.toString());
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nCONSULT failed because Client.Consult(String search) returned a RemoteException\n" + ex.getMessage() );
        }
        
        
    }
    
    
    /**
     * Set the result from a Consult action on the Client
     * @param transactionId Id of the transaction
     * @param result DocumentSummary List of the result
     */
    public void SetConsultResult(ArrayList<DocumentSummary> result, TransactionId transactionId) {
        this.TransactionResultConsultOccured(result, transactionId);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                              Free                                      */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * Read the document identified by DocumentId
     * @param docId DocumentId of the document to free
     */
    public void Free(DocumentId docId){
        try {
            this.state = State.FREEING_DOCUMENT;
            this.StateChanged(this.state.toString());
            this.server.Free(this.transactionId,docId);
            System.out.println("FREE SET : \n ID : "+docId.GetId()+"\nIP : "+docId.GetIp());
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nFREE failed because Client.Free(DocumentId docId) returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    /**
     * Set the result from a Free action on the Client
     * @param docId Id of the document which has been freed
     * @param result Boolean to give the result of the Add
     */
    public void SetFreeResult(Boolean result,DocumentId docId) {
        this.TransactionResultFreeOccured(result,docId);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                            Validate                                    */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * Validate the entire present transaction
     */
    public void Validate() {
        try {
            this.server.Validate(this.transactionId);
            this.state = State.VALIDATING_TRANSACTION;
            this.StateChanged(this.state.toString());
        } catch(RemoteException ex) {
            this.ErrorOccured("RemoteException : \nVALIDATE failed because Client.Validate() returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    
    /**
     * Set the result from a Validate action on the Client
     * @param result Boolean to give the result of the Validate
     */
    public void SetValidateResult(Boolean result) {
        this.TransactionResultValidateOccured(result);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                           Invalidate                                   */
    /*                                                                        */
    /*########################################################################*/
    
    /**
     * Invalidate the entire present transaction
     */
    public void Invalidate(){
        try {
            this.server.Invalidate(this.transactionId);
        } catch (RemoteException ex) {
            this.ErrorOccured("RemoteException : \nINVALIDATE failed because Client.Invalidate() returned a RemoteException\n" + ex.getMessage() );
        }
    }
    
    /**
     * Set the result from a Invalidate action on the Client
     * @param result Boolean to give the result of the Invalidate
     */
    public void SetInvalidateResult(Boolean result) {
        this.TransactionResultInvalidateOccured(result);
        this.state = State.WAITING_FOR_ACTION;
        this.StateChanged(this.state.toString());
    }
    
    
    /*########################################################################*/
    /*                                                                        */
    /*                        Services - Tools box                            */
    /*                                                                        */
    /*########################################################################*/
    
    
    /**
     * Return the name of the client's service
     * @return String which describe the service
     */
    public String GetServiceName(){
        return (this.service);
    }
    
    
    /**
     * Give the state of the registry
     * @return True if the registry is well initialyzed, otherwise false
     */
    public Boolean GetRegistryState(){
        if (this.registry == null){
            return false;
        } else {
            return true;
        }
    }
    
    /**
     * Give the ip of the Client
     * @return String describing the client
     */
    public String GetIp(){
        return this.ip;
    }
    
    public void Quit(){
        //TODO : Fermeture propre du client
    }
    
    private ArrayList<String> GetArrayList(String string){
        ArrayList<String> searchList = new ArrayList<String>();
        String[] stringSplit = string.split(" ");
        for(int i = 0; i<stringSplit.length ; i++) {
            searchList.add(stringSplit[i]);
        }
        
        return (searchList);
    }
}
